
#include"mesh.h"
#include"algfloat.h"
#include"vmesh.h" 
#include"VSphere.h" 
#include"vtorus.h" 
#include"dynarray.h"

static int n_boxes=0;

#define SUB_DIV 3

class BBox: public Box
{
  public:
  DynArray<int> triangles;
  DynArray<BBox*> children;
  BBox (float xmin,float xmax,float ymin,float ymax,float zmin,float zmax):
    Box(xmin,xmax,ymin,ymax,zmin,zmax)
  {
      n_boxes++;
      //printf("BBox %d %f %f %f %f %f %f\n",n_boxes, xmin,xmax,ymin,ymax,zmin,zmax);
  };
  void GenChildren (int b_h, unsigned int * v_ids, float * vertices)
  {
    if(b_h == 0)
      return;
    float x_step = (max[0]-min[0])/SUB_DIV;
    float y_step = (max[1]-min[1])/SUB_DIV;
    float z_step = (max[2]-min[2])/SUB_DIV;
    for(int ix=0;ix<SUB_DIV;ix++){
      float xmin = min[0]+ix*x_step;
      float xmax = xmin+x_step;
      for(int iy=0;iy<SUB_DIV;iy++){
        float ymin = min[1]+iy*y_step;
        float ymax = ymin+y_step;
        for(int iz=0;iz<SUB_DIV;iz++){
          float zmin = min[2]+iz*z_step;
          float zmax = zmin+z_step;
          BBox * tbox = new BBox(xmin,xmax,ymin,ymax,zmin,zmax);
          for(int it=0;it<triangles.nData;it++){
            //insere triangulo que tem pelo menos 
            //1 vertice dentro da box
            int t_id = triangles.data[it];
            bool inside=false;
            for(int vi=0;vi<3;vi++){
              int vid = v_ids[t_id*3+vi]*3;
              float * v = &vertices[vid];
              if(v[0]>=tbox->min[0] && v[0]<tbox->max[0] &&
                 v[1]>=tbox->min[1] && v[1]<tbox->max[1] &&
                 v[2]>=tbox->min[2] && v[2]<tbox->max[2] ){
                inside=true;
                break;
              }
            }
            if(inside)
              tbox->triangles.append(t_id);
          }
          tbox->triangles.truncate();
          //insere no children se a box tem ao menos 1 triangulo
          //printf("n_triangles %d\n",tbox->triangles.nData);
          if(tbox->triangles.nData > 0){
            children.append(tbox);
            tbox->GenChildren(b_h-1,v_ids,vertices);
          }
          else
            delete tbox;
        }
      }
    }
  };
};

void Mesh::ShapeFromFile(const char * filename)
{
  shape = new VMesh(filename); 
  Init();
}

void Mesh::ShapeSphere(int n)
{
  shape = new VSphere(n,false); 
  Init();
}

void Mesh::ShapeTorus(int n)
{
  shape = new VTorus(n); 
  Init();
}

void Mesh::Init ()
{
  vertices = shape->GetVertArray();
  triangles = shape->GetTriangleArray();
  normals = shape->GetNormalArray();
  float xmin, xmax, ymin, ymax, zmin, zmax;
  shape->GetBBox(&xmin, &xmax, &ymin, &ymax, &zmin, &zmax);
  box = new BBox(xmin, xmax, ymin, ymax, zmin, zmax);
  unsigned int n_triangles = shape->GetNTriangles();
  printf("n_triangles=%d\n",n_triangles);
  for(int it=0; it<n_triangles; it++){
    box->triangles.append(it);
  }
  box->triangles.truncate();
  box->GenChildren(box_h, triangles, vertices);
}

bool Mesh::Hit_aux (Ray * ray, BBox * bx, float* t_p, float* t_d)
{
  bool shadow = ray->shadow_mode;
  ray->shadow_mode = true;
  bool hit = bx->Hit_aux(ray, t_p, t_d);
  ray->shadow_mode = shadow;
  if(!hit)
    return false;
  if(bx->children.nData==0){
    int n = bx->triangles.nData;
    int *tris = bx->triangles.data;
    if(shadow){
      for(int i=0;i<n;i++){
        if(TriangleHit(ray,tris[i],t_p,t_d)){
          return true;
        }
      }
      return false;
    }
    else{
      bool ret = false;
      //printf("n_triangles=%d\n",n_triangles);
      for(int i=0;i<n;i++){
        if(TriangleHit(ray,tris[i],t_p,t_d)){
          ret=true;
        }
      }
      return ret;
    }
  }
  else{
    int n = bx->children.nData;
    BBox** children = bx->children.data;
    if(shadow){
      for(int i=0;i<n;i++){
        if(Hit_aux(ray,children[i],t_p,t_d)){
          return true;
        }
      }
      return false;
    }
    else{
      bool ret = false;
      //printf("n_triangles=%d\n",n_triangles);
      for(int i=0;i<n;i++){
        if(Hit_aux(ray,children[i],t_p,t_d)){
          ret=true;
        }
      }
      return ret;
    }
  }
}

bool Mesh::Hit (Ray * ray)
{
  float t_p[4];
  float t_d[4];
  bool shadow = ray->shadow_mode;
  int level = shadow?SHADOW_RAY:VIEW_RAY;
  AFMatXVec(mat_inv,ray->GetP(level),t_p);
  AFMatXVec(mat_inv,ray->GetD(level),t_d);
  return Hit_aux(ray,box,t_p,t_d);
}

bool Mesh::TriangleHit (Ray * ray, int t_id, float * t_p,  float * t_d)
{
  bool shadow = ray->shadow_mode;
  int v0id = triangles[t_id*3]*3;
  int v1id = triangles[t_id*3+1]*3;
  int v2id = triangles[t_id*3+2]*3;
  float * v0 = &vertices[v0id];
  float * v1 = &vertices[v1id];
  float * v2 = &vertices[v2id];
  float t;
  float A = v0[0] - v1[0];
  float B = v0[1] - v1[1];
  float C = v0[2] - v1[2];
  float D = v0[0] - v2[0];
  float E = v0[1] - v2[1];
  float F = v0[2] - v2[2];
  float G = t_d[0];
  float H = t_d[1];
  float I = t_d[2];
  float J = v0[0] - t_p[0];
  float K = v0[1] - t_p[1];
  float L = v0[2] - t_p[2];
  float EIHF = E*I-H*F;
  float GFDI = G*F-D*I;
  float DHEG = D*H-E*G;
  float denom = (A*EIHF + B*GFDI + C*DHEG) ;
  if(denom==0.0)
    return false;
  float beta = (J*EIHF + K*GFDI + L*DHEG) / denom;
  if (beta <= 0.0 || beta >= 1.0) 
    return false;
  float AKJB = A*K - J*B;
  float JCAL = J*C - A*L;
  float BLKC = B*L - K*C;
  float gamma = (I*AKJB + H*JCAL + G*BLKC)/denom;
  if (gamma <= 0.0 || beta + gamma >= 1.0) 
    return false;
  t = -(F*AKJB + E*JCAL + D*BLKC) / denom;
  if(t<0)
    return false;
  if(shadow){
    if(t<=ray->tmax){
      //printf("tri shadow id=%d t=%f tmax=%f ret=%s tmin=%f obj=%d\n",ray->id,t, ray->tmax, t<=ray->tmax?"true":"false",ray->tmin,ray->shadow_obj);
      return true;
    }
    return false;
  }
  if(t<ray->tmin || ray->tmin==-1.0){
    //printf("tri id=%d t=%f tmin=%f\n",ray->id,t, ray->tmin);
    float * ps = ray->GetP(SHADOW_RAY); 
    ray->CalcPoint(VIEW_RAY,t,ps);
    float * normal = ray->normal;
    float * n0 = &normals[v0id];
    float * n1 = &normals[v1id];
    float * n2 = &normals[v2id];
    float tnorm[4];
    float alfa = 1-beta-gamma;
    tnorm[0] = alfa*n0[0] + beta*n1[0] + gamma*n2[0];
    tnorm[1] = alfa*n0[1] + beta*n1[1] + gamma*n2[1];
    tnorm[2] = alfa*n0[2] + beta*n1[2] + gamma*n2[2];
    tnorm[3] = 0.0;
    AFMatXVec(mat_it,tnorm,normal);
    AFUnit(normal);
    ray->tmin = t;
    SetRayAtt(ray);
    float* rcolor = ray->obj_color;
    return true;
  }
  return false;
}

